package com.write.spring.ioc.core;

import com.write.spring.ioc.annotation.*;
import com.write.spring.ioc.util.ScopeEnum;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <li>扫描配置类，通过配置类配置的 ComponentScan 获取路径，获取该路径下的 class 文件，获取到 Class 对象列表。</li>
 * <li>遍历 Class 对象列表，判断是否有 Component 注解，如果有封装为 BeanDefinition 对象，解析其他的注解填充
 * BeanDefinition 的属性，最后放入 BeanDefinitionMap 中，key是 beanName， value 是 BeanDefinition 对象。</li>
 * <li>如果 Class 对象实现了 BeanPostProcessor ,实例化之后放入 BeanPostProcessor 对象列表中。</li>
 * <li>实例化非懒加载单例 bean   </li>
 */
public class ApplicationContext {

    /**
     * 缓存 BeanDefinition，key 为 beanName，value 为 BeanDefinition
     */
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    /**
     * 单例池，缓存单例对象
     */
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    /**
     * 缓存 BeanPostProcessor
     */
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public ApplicationContext(Class configClass) {
        // 扫描生成BeanDefinition，并放入beanDefinitionMap中
        scan(configClass);
        /**
         * 实例化非懒加载单例bean
         * 1、实例化
         * 2、属性填充
         * 3、Aware回调
         * 4、初始化
         * 5、添加到单例池
         */
        instanceSingletonBean();
    }

    /**
     * 创建非懒加载的单例bean
     */
    private void instanceSingletonBean() {

        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            BeanDefinition beanDefinition = beanDefinitionEntry.getValue();
            if (beanDefinition.getScope().equals(ScopeEnum.SINGLETON.getScope())) {
                Object bean = doCreateBean(beanName, beanDefinition);
                if (bean != null)
                    singletonObjects.put(beanName, bean);
            }
        }

    }

    /**
     * 实例化bean
     * @param beanName bean的名称
     * @param beanDefinition bean的定义
     * @return
     */
    private Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
        if (beanDefinition == null) {
            throw new NullPointerException("beanDefinition is null");
        }

        Class beanClass = beanDefinition.getBeanClass();
        try {
            // 实例化
            Object obj = beanClass.getDeclaredConstructor().newInstance();

            // 属性填充
            fillAttribute(beanClass, obj, beanName);

            System.out.println("finish init -> " + beanName);

            // 处理BeanPostProcessor
            doBeanPostProcessor(beanName, obj);

            return obj;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void doBeanPostProcessor(String beanName, Object obj) {
        for (BeanPostProcessor beanPostProcessor: beanPostProcessorList) {
            beanPostProcessor.postProcessAfterInitialization(beanName, obj);
        }
    }

    private void fillAttribute(Class beanClass, Object obj, String beanName) throws IllegalAccessException {
        Field[] declaredFields = beanClass.getDeclaredFields();

        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                // byname  在Spring中的Autowire是先byType，再byName
                 String fieldName = field.getName();
                Object bean = getBean(fieldName);
                field.setAccessible(true);
                field.set(obj, bean);
            }
        }

        if (obj instanceof BeanNameWare) {
            ((BeanNameWare) obj).setBeanName(beanName);
        }
    }

    /**
     * 扫描配置类路径，初始化BeanDefinition，放入beanDefinitionMap中
     * @param configClass 指定配置类
     */
    private void scan(Class configClass) {
        ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if (componentScan == null || componentScan.value() == null) {
            throw new RuntimeException("not specified ComponentScan -> " + configClass.getName());
        } else {
            doComponentAnno(componentScan);
        }
    }

    /**
     * 通过 ComponentScan 路径获取到 class 对象
     * @param componentScan
     */
    private void doComponentAnno(ComponentScan componentScan) {
        List<Class> classes = getBeanClass(componentScan.value());
        // 遍历 class 列表，将标有 Component 注解的 class 生成 BeanDefinition
        // 并将生成的 BeanDefinition 放入 BeanDefinitionMap 中
        for (Class clazz : classes) {
            parseComponentAnno(clazz);
        }
    }

    private void parseComponentAnno(Class clazz) {
        if (clazz.isAnnotationPresent(Component.class)) {
            String beanName = ((Component) clazz.getAnnotation(Component.class)).value();
            if ("".equals(beanName))
                System.out.println("not set component value -> " + clazz.getName());
            // 封装 BeanDefinition
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            // 解析属性
            parseAnnotation(clazz, beanDefinition);
            // 解析后置处理器
            parseBeanPostProccessor(clazz);
            // 放入 BeanDefinitionMap 中
            beanDefinitionMap.put(beanName, beanDefinition);
        }
    }

    private void parseAnnotation(Class clazz, BeanDefinition beanDefinition) {
        parseScope(clazz, beanDefinition);

        parseLazy(clazz, beanDefinition);
    }

    private void parseLazy(Class clazz, BeanDefinition beanDefinition) {
        if (clazz.isAnnotationPresent(Lazy.class)) {
            beanDefinition.setLazy(true);
        }
    }

    private void parseBeanPostProccessor(Class clazz) {
        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
            try {
                beanPostProcessorList.add((BeanPostProcessor) clazz.getDeclaredConstructor().newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }

    private void parseScope(Class clazz, BeanDefinition beanDefinition) {
        if (clazz.isAnnotationPresent(Scope.class)) {
            String scope = ((Scope) clazz.getAnnotation(Scope.class)).value();
            beanDefinition.setScope(scope);
        } else {
            beanDefinition.setScope(ScopeEnum.SINGLETON.getScope());
        }
    }

    /**
     * 获取指定路径下的 class 文件列表
     * @param filePath
     * @return
     */
    private List<Class> getBeanClass(String filePath) {
        List<Class> classes = new ArrayList<>();
        // 类路径
        ClassLoader classLoader = ApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(filePath.replace(".", "/"));
        File file = new File(resource.getFile());
        // TODO 递归
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                // Spring中采用的是ASM的技术来判断的有没有指定的注解
                String fileName = f.getAbsolutePath();
                if (fileName.endsWith(".class")) {
                    String className = filePath + "." +
                            fileName.substring(fileName.lastIndexOf("\\") + 1,
                                    fileName.lastIndexOf(".class"));
                    try {
                        Class<?> clazz = classLoader.loadClass(className);
                        classes.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            throw new RuntimeException("not a directory -> " + filePath);
        }
        return classes;
    }

    /**
     * 先看单例池中是否，如果有直接返回；没有，执行doCreateBean，实例化bean
     *
     * @param beanName bean的名称
     * @return bean对象
     */
    public Object getBean(String beanName) {
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else {
            return doCreateBean(beanName, beanDefinitionMap.get(beanName));
        }
    }

}
